﻿using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.Domain;
using K9Nano.Runtime;
using Microsoft.EntityFrameworkCore.Storage;

namespace K9Nano.EFCore
{
    public class EfCoreUnitOfWork<TDbContext> : IUnitOfWork where TDbContext : class, IK9DbContext
    {
        private readonly TDbContext _dbContext;

        private IDbContextTransaction _transaction;

        private int _transactionCount = 0;

        public EfCoreUnitOfWork(TDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public virtual IRepository<TEntity, TKey> Repository<TEntity, TKey>()
            where TEntity : class, IEntity<TKey>
            where TKey : IEquatable<TKey>
        {
            return new EfRepositoryBase<TEntity, TKey, TDbContext>(_dbContext);
        }

        public virtual IRepository<TEntity> Repository<TEntity>() where TEntity : Entity
        {
            return new EfRepositoryBase<TEntity, TDbContext>(_dbContext);
        }

        public void SaveChanges(bool acceptAllChangesOnSuccess = true)
        {
            _dbContext.SaveChanges(acceptAllChangesOnSuccess);
        }

        public Task SaveChangesAsync(bool acceptAllChangesOnSuccess = true, CancellationToken cancellation = default)
        {
            return _dbContext.SaveChangesAsync(acceptAllChangesOnSuccess, cancellation);
        }

        public virtual IDisposable DisableFilters()
        {
            _dbContext.FiltersDisabled = true;
            return new DisposeAction(() => _dbContext.FiltersDisabled = false);
        }

        public virtual IDisposable DisableAutoSetTenant()
        {
            _dbContext.SuppressAutoSetTenantId = true;
            return new DisposeAction(() => _dbContext.SuppressAutoSetTenantId = false);
        }

        public virtual IDisposable DisableEvents()
        {
            _dbContext.EventsDisabled = true;
            return new DisposeAction(() => _dbContext.EventsDisabled = false);
        }

        public virtual void BeginTransaction()
        {
            lock (this)
            {
                if (_transaction == null)
                {
                    _transaction = _dbContext.BeginTransaction();
                    _transactionCount = 0;
                } 
            }

            Interlocked.Increment(ref _transactionCount);
        }

        public void Commit()
        {
            Interlocked.Decrement(ref _transactionCount);
            lock (this)
            {
                if (_transactionCount <= 0)
                {
                    _transaction.Commit();
                    _transaction.Dispose();
                    _transaction = null;
                }
            }
        }

        public void Rollback()
        {
            Interlocked.Decrement(ref _transactionCount);
            lock (this)
            {
                if (_transactionCount <= 0)
                {
                    if (_transaction == null) return;
                    _transaction.Rollback();
                    _transaction.Dispose();
                    _transaction = null;
                }
            }
        }


        private class EfCoreDbTransaction : IDbTransaction
        {
            private readonly IDbContextTransaction _transaction;

            public EfCoreDbTransaction(IDbContextTransaction transaction)
            {
                _transaction = transaction;
            }

            public void Dispose()
            {
                _transaction.Dispose();
            }

            public void Commit()
            {
                _transaction.Commit();
            }

            public void Rollback()
            {
                _transaction.Rollback();
            }

            public IDbConnection Connection => _transaction.GetDbTransaction().Connection;

            public IsolationLevel IsolationLevel => _transaction.GetDbTransaction().IsolationLevel;
        }
    }
}